package com.duniptech.soa.service;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.PrintStream;
import java.io.StringReader;
import java.net.InetAddress;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import com.duniptech.soa.simulation.Coordinator;
import com.duniptech.soa.simulation.Simulator;
import com.duniptech.soa.util.Util;


public class Simulation implements SimulationServiceInterface {
	public static HashMap<String, Simulator> simulators = new HashMap<String, Simulator>();
	public static PrintStream reporter = null;
	
	public String simulate(String clientIp, String serverPackage, String xmlModelAsString, long numIterations) throws Exception {

		Document xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(xmlModelAsString)));
		Element xmlModel = (Element)xmlDocument.getElementsByTagName("coupled").item(0);
		
		// Update the name and class attributes
		xmlModel.setAttribute("name", xmlModel.getAttribute("name") + "@" + clientIp);
		
		NodeList xmlComponentList = xmlModel.getElementsByTagName("coupled");
		for(int i=0; i<xmlComponentList.getLength(); ++i) {
			Element xmlComponent = (Element)xmlComponentList.item(i);
			xmlComponent.setAttribute("name", xmlComponent.getAttribute("name") + "@" + clientIp);
			xmlComponent.setAttribute("class", serverPackage + "." + xmlComponent.getAttribute("class"));
		}
		
		xmlComponentList = xmlModel.getElementsByTagName("atomic");
		for(int i=0; i<xmlComponentList.getLength(); ++i) {
			Element xmlComponent = (Element)xmlComponentList.item(i);
			xmlComponent.setAttribute("name", xmlComponent.getAttribute("name") + "@" + clientIp);
			xmlComponent.setAttribute("class", serverPackage + "." + xmlComponent.getAttribute("class"));
		}

		NodeList xmlPortList = xmlModel.getElementsByTagName("inport");
		for(int i=0; i<xmlPortList.getLength(); ++i) {
			Element xmlPort = (Element)xmlPortList.item(i);
			xmlPort.setAttribute("class", serverPackage + "." + xmlPort.getAttribute("class"));
		}

		xmlPortList = xmlModel.getElementsByTagName("outport");
		for(int i=0; i<xmlPortList.getLength(); ++i) {
			Element xmlPort = (Element)xmlPortList.item(i);
			xmlPort.setAttribute("class", serverPackage + "." + xmlPort.getAttribute("class"));
		}
		
		NodeList xmlConnectionList = xmlModel.getElementsByTagName("connection");
		for(int i=0; i<xmlConnectionList.getLength(); ++i) {
			Element xmlConnection = (Element)xmlConnectionList.item(i);
			xmlConnection.setAttribute("atomicFrom", xmlConnection.getAttribute("atomicFrom") + "@" + clientIp);
			xmlConnection.setAttribute("atomicTo", xmlConnection.getAttribute("atomicTo") + "@" + clientIp);
		}

		// Create the coordinator:
		String rootCoupledName = xmlModel.getAttribute("name");
		Coordinator coordinator = new Coordinator(rootCoupledName, xmlModel);
		StringBuffer console = new StringBuffer();
		String response = coordinator.simulate(numIterations);
		console.append(response);
		coordinator.exit();
		return console.toString();
	}
	
	public void newSimulator(String xmlAtomicAsString) throws Exception {
		Document xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(xmlAtomicAsString)));
		Element xmlComponent = (Element)xmlDocument.getFirstChild();
		String name = xmlComponent.getAttribute("name");
		Simulator simulator = new Simulator(xmlComponent);
		if(Util.debug) System.out.println("Simulator for " + name + " created at:" + simulator);
		simulators.put(name, simulator);
		if(reporter==null) {
			int posAt = name.indexOf("@");
			String clientIp = name.substring(posAt+1, name.length());
			reporter = new PrintStream(new BufferedOutputStream(new FileOutputStream(new File(Util.LOG_DIR + File.separator + clientIp + ".log"))),true);
			System.setOut(reporter);
		}
	}
	
	public void initialize(String key, double t) {
		if(Util.debug) System.out.println("initialize(...):Getting simulator " + key);
		simulators.get(key).initialize(t);
	}
	
	public double getTN(String key) throws Exception {
		if(Util.debug) System.out.println("getTN(...):Getting simulator " + key);
		return simulators.get(key).getTN();
	}
	
	public void lambda(String key, double t) throws Exception {
		simulators.get(key).lambda(t);
	}
	
	public String[] getOutput(String key, String portName) throws Exception {
		return simulators.get(key).getOutput(portName);
	}
	
	public void receive(String key, String simulatorFrom, String portFrom, String simulatorTo, String portTo, String[] xmlValues) throws Exception {
		simulators.get(key).receive(simulatorFrom, portFrom, simulatorTo, portTo, xmlValues);
	}
	
	public void deltfcn(String key, double t) throws Exception {
		simulators.get(key).deltfcn(t);
	}
	
	public String getConsole(String clientIp) throws Exception {
		if(reporter==null) return "";
		StringBuffer console = new StringBuffer();
		File file = new File(Util.LOG_DIR + File.separator + clientIp + ".log");
		if(!file.exists()) return "File " + file.getAbsolutePath() + " does not exist.\n";
		BufferedReader reader = new BufferedReader(new FileReader(file));
		console.append(InetAddress.getLocalHost().getHostAddress() + " output:\n");
		String line = "";
		while((line=reader.readLine())!=null) console.append(line + "\n");
		reporter = null;
		return console.toString();		
	}
	
	public void exit(String key) throws Exception {
		simulators.put(key, null);
		System.gc();		
	}

	
}
